#include "LuaFile.h"
#include "LuaTable.h"
#include <lua.hpp>
#include <cstdio>

LuaFile* LuaFile::CURRENT_PARSER = NULL;
using std::string;
using std::set;



/***************************************************************************/

LuaFile::LuaFile(const string& filename, const string& accessMode) :
	m_szFile(filename),
	m_szAccessMode(accessMode),
	m_szChunk(""),
	m_szErrorLog(""),
	m_RootRef(LUA_NOREF),
	m_CurRef(LUA_NOREF),
	m_bValid(false)
{
	L = luaL_newstate();
	if (L != NULL)
		luaL_openlibs(L);
}


LuaFile::LuaFile(const string& chunk) :
	m_szFile(""),
	m_szAccessMode(""),
	m_szChunk(chunk),
	m_szErrorLog(""),
	m_RootRef(LUA_NOREF),
	m_CurRef(LUA_NOREF),
	m_bValid(false)
{
	L = luaL_newstate();
	if (L != NULL)
		luaL_openlibs(L);
}


LuaFile::~LuaFile()
{
	if (L != NULL)
	{
		lua_close(L);
		L = NULL;
	}

	set<LuaTable*>::iterator it;
	for (it = m_Tables.begin(); it != m_Tables.end(); ++it)
	{
		LuaTable& table		= **it;
		table.m_LuaFile		= NULL;
		table.L				= NULL;
		table.m_bIsValid	= false;
		table.m_RefNum		= LUA_NOREF;
	}
}


/***************************************************************************/

bool LuaFile::Execute()
{
	// Ensure the Lua state was created
	if (L == NULL) 
	{
		m_szErrorLog = "Failed to create Lua state";
		lua_close(L);
		L = NULL;
		return false;
	}

	m_RootRef = LUA_NOREF;

	// Ensure either the chunk or file (and access mode) aren't empty
	if (m_szChunk.empty() && (m_szFile.empty() && m_szAccessMode.empty()))
	{
		m_szErrorLog = "No file or chunk loaded";
		lua_close(L);
		L = NULL;
		return false;
	}

	// If the chunk is empty, read the file
	if (m_szChunk.empty() && (!m_szFile.empty() && !m_szAccessMode.empty()))
	{
		// Open the filename
		FILE* file = NULL;
		int err = fopen_s(&file, m_szFile.c_str(), m_szAccessMode.c_str());

		// Make sure file exists
		if (err != 0 || file == NULL)
		{
			m_szErrorLog = "Failed to open: " + m_szFile;
			lua_close(L);
			L = NULL;
			return false;
		}

		// Read the file into memory
		while (!feof(file))
		{
			char c = fgetc(file);
			if (c != EOF)
				m_szChunk += c;
		}

		// Close the file
		fclose(file);
	}

	// Execute the Lua script from the now-filled chunk
	string name = (!m_szFile.empty() ? m_szFile.c_str() : "chunk");

	// Load and compile the buffer
	int error = luaL_loadbuffer(L, m_szChunk.c_str(), m_szChunk.size(), name.c_str());
	if (error != 0)
	{
		m_szErrorLog = lua_tostring(L, -1);
		lua_pop(L, 1);

		lua_close(L);
		L = NULL;
		return false;
	}
	
	// Set the current parser to this so functions know to load from this Lua file and execute it
	CURRENT_PARSER = this;
	error = lua_pcall(L, 0, 1, 0);
	CURRENT_PARSER = NULL;

	// Get the error log if something went wrong
	if (error != 0)
	{
		m_szErrorLog = lua_tostring(L, -1);
		lua_pop(L, 1);

		lua_close(L);
		L = NULL;
		return false;
	}

	// Make sure the return value is a table
	if (!lua_istable(L, 1))
	{
		m_szErrorLog = "Missing return table from " + m_szFile;

		lua_close(L);
		L = NULL;
		return false;
	}

	// Register the table at the top of the stack
	m_RootRef = luaL_ref(L, LUA_REGISTRYINDEX);

	// Clear the stack
	lua_settop(L, 0);

	m_bValid = true;

	return true;
}


/***************************************************************************/

LuaTable LuaFile::GetRoot()
{
	return LuaTable(this);
}


LuaTable LuaFile::SubTableExpr(const string& expr)
{
	return GetRoot().SubTableExpr(expr); 
}